use std::collections::HashMap;
use syn::{File, ItemEnum, ItemFn, ItemMacro, ItemStruct, ItemUnion, ItemUse, ImplItem, Type};
use syn::visit::Visit;
use quote::ToTokens;
//use levenshtein::levenshtein;
use strsim::jaro_winkler;

use crate::codechecker::CodeChecker;

#[derive(Debug, Clone)]
pub enum Symbol {
    Crate { use_stmt: String },
    Module { name: String },
    Constant { name: String, ty: String },
    Static { name: String, ty: String },
    Macro { name: String, content: String },
    Struct { name: String, fields: SymbolTable },
    StructField { name: String, ty: String },
    Enum { name: String, variants: SymbolTable },
    EnumVariant {name: String, fields: SymbolTable},
    Union { name: String, fields: SymbolTable },
    Impl { type_name: String, methods: SymbolTable },
    Function { summary: FunctionSummary },
    Method { summary: FunctionSummary },
}

#[derive(Debug, Clone)]
struct FunctionSummary {
    name: String,
    is_unsafe: bool,
    inputs: Vec<(String, String)>,
    output: Option<String>,
}

#[derive(Debug, Clone)]
struct VariantSummary {
    name: String,
    fields: Vec<(String, String)>,
}

#[derive(Debug, Clone)]
pub struct SymbolTable {
    pub symbols: HashMap<String, Symbol>,
}

impl SymbolTable {
    pub fn new() -> Self {
        let table = SymbolTable {
            symbols: HashMap::new(),
        };
        table
    }

    fn add_common_crates(&mut self) {
        let common_crates = vec![
            "use gmp::mpz::Mpz;",
            "use libc;",
            "use libc::strcpy;",
            "use libc::strdup;",
            "use libc::strlen;",
            "use libc::free;",
            "use num_bigint::BigUint;",
            "use num_complex::Complex;",
            "use num_traits::{One, Zero};",
            "use rand;",
            "use rand::Rng;",
            "use regex::Regex;",
            "use serde::{Serialize, Deserialize};",
            "use serde_json::json;",
            "use sscanf::sscanf;",
            "use std::assert;",
            "use std::cmp::Ordering;",
            "use std::cmp::min;",
            "use std::collections::HashMap;",
            "use std::collections::HashSet;",
            "use std::f64;",
            "use std::ffi::CStr;",
            "use std::ffi::CString;",
            "use std::fmt::Debug;",
            "use std::fs::File;",
            "use std::io::{self, stdin, stdout, Read, Write, BufRead};",
            "use std::ops::Add;",
            "use std::os::raw::c_char;",
            "use std::os::raw::c_void;",
            "use std::panic::AssertUnwindSafe;",
            "use std::path::Path;",
            "use std::process;",
            "use std::process::Command;",
            "use std::ptr;",
            "use std::ptr::null_mut;",
            "use std::rc::Rc;",
            "use std::str;",
            "use std::str::FromStr;",
            "use std::thread;",
            "use std::time::Duration;",
            "use std::{io, env};",
        ];

        for use_stmt in common_crates {
            if use_stmt.contains("::{") {
                let base = use_stmt.split('{').next().unwrap().to_string();
                let inner = use_stmt.split('{').nth(1).unwrap().split('}').next().unwrap();
                let items: Vec<&str> = inner.split(',').map(|s| s.trim()).collect();
                for item in items {
                    self.insert(item.to_string(), Symbol::Crate {use_stmt: format!("{}{};", base, item)});
                }
            } else {
                if let Some(last_segment) = use_stmt.split("::").last() {
                    let last_segment = last_segment.trim_end_matches(";");
                    self.insert(last_segment.to_string(), Symbol::Crate {use_stmt: use_stmt.to_string()});
                }

            }
        }
    }

    pub fn insert(&mut self, name: String, symbol: Symbol) {
        self.symbols.insert(name, symbol);
    }

    pub fn get(&self, name: &str) -> Option<&Symbol> {
        self.symbols.get(name)
    }

    pub fn find_closest(&self, name: &str, threshold: f64) -> Option<(String, f64)> {
        let closest = self.symbols.keys()
            .filter_map(|key| {
                let similarity = jaro_winkler(key, name);
                Some((key.clone(), similarity))
            })
            .max_by(|a, b| a.1.partial_cmp(&b.1).unwrap());

        if let Some((ref key, similarity)) = closest {
            if similarity >= threshold {
                //println!("Closest match(>{}) is '{}' with similarity {:.2}", threshold, key, similarity);
                Some((key.clone(), similarity))
            } else {
                //println!("Closest match(<={}) is '{}' with similarity {:.2}", threshold, key, similarity);
                //closest
                None
            }
        } else {
            None
        }
    }

    pub fn get_struct_fields(&self, struct_name: &str) -> Option<&SymbolTable> {
        match self.get(struct_name) {
            Some(Symbol::Struct { fields, ..}) => Some(fields),
            _ => None,
        }      
    } 

    //pub fn find_closest(&self, name: &str, threshold: f64) -> Option<(String, f64)> {
    //    let closest = self.symbols.keys()
    //        .filter_map(|key| {
    //            let distance = levenshtein(key, name) as f64;
    //            let similarity = 1.0 - distance / key.len().max(name.len()) as f64;
    //            Some((key.clone(), similarity))
    //        })
    //        .max_by(|a, b| a.1.partial_cmp(&b.1).unwrap());

    //    if let Some((ref key, similarity)) = closest {
    //        if similarity >= threshold {
    //            Some((key.clone(), similarity))
    //        } else {
    //            println!("Closest match is '{}' with similarity {:.2}", key, similarity);
    //            closest
    //        }
    //    } else {
    //        None
    //    }    
    //}
}

pub struct RustAnalyzer {
    symbol_table: SymbolTable,
    context_code: String,
}

impl RustAnalyzer {
    pub fn new(code: String) -> Self {
        RustAnalyzer {
            symbol_table: SymbolTable::new(),
            context_code: code,
        }
    }

    pub fn analyze(&mut self, file: &File) {
        self.visit_file(file);
    }

    pub fn print_summary(&self) -> String {
        format!("{:#?}", self.symbol_table)
    }

    pub fn check_and_fix_code(&mut self, code: &str) -> (String, String, Vec<String>) {
        let mut fixed_code = code.to_string();
        let mut errors = Vec::new();
        let mut context_code = self.context_code.clone(); 
        loop {
            let syntax_tree = syn::parse_file(&fixed_code).expect("Unable to parse file");
            let mut crate_symbol_table = SymbolTable::new();
            crate_symbol_table.add_common_crates();
            let mut visitor = CodeChecker {
                symbol_table: &mut self.symbol_table,
                fixed_code: fixed_code.clone(),
                context_code: context_code.clone(),
                errors: Vec::new(),
                crate_symbol_table: &mut crate_symbol_table,
                added_use_statements: Vec::new(),
            };
            
            visitor.visit_file(&syntax_tree);
            fixed_code = visitor.fixed_code;
            context_code = visitor.context_code.clone();
            //println!("{:?}", visitor.errors);
            if visitor.errors == errors {
                break;
            } else {
                errors = visitor.errors.clone();
            }
        }

        (fixed_code, context_code, errors)
        
    }

}

impl<'ast> Visit<'ast> for RustAnalyzer {
    fn visit_item_use(&mut self, node: &'ast ItemUse) {
        let path = node.tree.to_token_stream().to_string();
        if path.contains("::*") {
            self.symbol_table.insert(path.clone(), Symbol::Crate { use_stmt: path });
        } else {
            let segments: Vec<&str> = path.split("::").collect();
            let name = segments.last().unwrap().to_string();
            self.symbol_table.insert(name.clone(), Symbol::Crate { use_stmt: path });
        }
    }

    fn visit_item_macro(&mut self, node: &'ast ItemMacro) {
        let name = node.mac.path.segments.last().unwrap().ident.to_string();
        let content = node.mac.tokens.to_string();
        self.symbol_table.insert(name.clone(), Symbol::Macro { name, content });
    }

    fn visit_item_fn(&mut self, node: &'ast ItemFn) {
        let is_unsafe = node.sig.unsafety.is_some();
        let name = node.sig.ident.to_string();
        let inputs = node.sig.inputs.iter().map(|arg| {
            match arg {
                syn::FnArg::Receiver(receiver) => {
                    let mutability = if receiver.mutability.is_some() { "&mut " } else { "&" };
                    ("self".to_string(), format!("{}self", mutability))
                },
                syn::FnArg::Typed(pat_type) => {
                    let pat = pat_type.pat.to_token_stream().to_string();
                    let ty = pat_type.ty.to_token_stream().to_string();
                    (pat, ty)
                }
            }
        }).collect();

        let output = match &node.sig.output {
            syn::ReturnType::Default => None,
            syn::ReturnType::Type(_, ty) => Some(ty.to_token_stream().to_string()),
        };

        let function_summary = FunctionSummary {
            name: name.clone(),
            is_unsafe,
            inputs,
            output,
        };

        self.symbol_table.insert(name.clone(), Symbol::Function { summary: function_summary });
    }

    fn visit_item_struct(&mut self, node: &'ast ItemStruct) {
        let name = node.ident.to_string();
        let mut field_table = SymbolTable::new();
        for field in node.fields.iter() {
            let field_name = field.ident.as_ref().unwrap().to_string();
            let ty = field.ty.to_token_stream().to_string();
            field_table.insert(field_name.clone(), Symbol::StructField { name: field_name, ty });
        }
        self.symbol_table.insert(name.clone(), Symbol::Struct { name, fields: field_table });
    }

    fn visit_item_enum(&mut self, node: &'ast ItemEnum) {
        let name = node.ident.to_string();
        let mut variant_table = SymbolTable::new();
        for variant in node.variants.iter() {
            let variant_name = variant.ident.to_string();
            let mut field_table = SymbolTable::new();
            match &variant.fields {
                syn::Fields::Unit => {},
                syn::Fields::Unnamed(fields) => { 
                    for (index, field) in fields.unnamed.iter().enumerate() {
                        let field_name = format!("field{}", index);
                        let ty = field.ty.to_token_stream().to_string();
                        field_table.insert(field_name.clone(), Symbol::StructField { name: field_name, ty: ty });
                    }
                },
                syn::Fields::Named(fields) => {
                    for field in fields.named.iter() {
                        let field_name = field.ident.as_ref().unwrap().to_string();
                        let ty = field.ty.to_token_stream().to_string();
                        field_table.insert(field_name.clone(), Symbol::StructField { name: field_name, ty: ty });
                    }
                }
            };
            variant_table.insert(variant_name.clone(), Symbol::EnumVariant {name: variant_name, fields: field_table});
        }
        self.symbol_table.insert(name.clone(), Symbol::Enum { name, variants: variant_table });
    }

    fn visit_item_union(&mut self, node: &'ast ItemUnion) {
        let name = node.ident.to_string();
        let mut field_table = SymbolTable::new();
       
        for field in node.fields.named.iter() {
            let field_name = field.ident.as_ref().unwrap().to_string();
            let ty = field.ty.to_token_stream().to_string();
            field_table.insert(field_name.clone(), Symbol::StructField { name: field_name, ty });
        }

        self.symbol_table.insert(name.clone(), Symbol::Union { name, fields:field_table });
    }

    fn visit_item_impl(&mut self, node: &'ast syn::ItemImpl) {
        if let Type::Path(type_path) = &*node.self_ty {
            let type_name = type_path.path.segments.iter().map(|segment| segment.ident.to_string()).collect::<Vec<String>>().join("::");
            let mut method_table = SymbolTable::new();

            for item in &node.items {
                if let ImplItem::Fn(method) = item {
                    let is_unsafe = method.sig.unsafety.is_some();
                    let name = method.sig.ident.to_string();
                    let inputs = method.sig.inputs.iter().map(|arg| {
                        match arg {
                            syn::FnArg::Receiver(receiver) => {
                                let mutability = if receiver.mutability.is_some() { "&mut " } else { "&" };
                                ("self".to_string(), format!("{}self", mutability))
                            },
                            syn::FnArg::Typed(pat_type) => {
                                let pat = pat_type.pat.to_token_stream().to_string();
                                let ty = pat_type.ty.to_token_stream().to_string();
                                (pat, ty)
                            }
                        }
                    }).collect();

                    let output = match &method.sig.output {
                        syn::ReturnType::Default => None,
                        syn::ReturnType::Type(_, ty) => Some(ty.to_token_stream().to_string()),
                    };

                    let function_summary = FunctionSummary {
                        name: name.clone(),
                        is_unsafe,
                        inputs,
                        output,
                    };

                    method_table.insert(name.clone(), Symbol::Method { summary: function_summary });
                }
            }

            self.symbol_table.insert(format!("impl::{}", type_name.clone()), Symbol::Impl { type_name, methods: method_table });
        }
    }
}
